﻿using ElasticSearchTest.Common;
using Nest;
using PMS.Search.Domain.Common;
using PMS.Search.Domain.Entities;
using PMS.Search.Elasticsearch;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using Utils.Helper;

namespace ElasticSearchTest
{
    public class TopicEsService
    {
        private readonly EsClient _client;
        private readonly EsIndexConfig.Index _index = EsIndexConfig.Instance.TopicIndex;

        public TopicEsService(EsClient client)
        {
            _client = client;
        }

        public void Test()
        {
            var keyword = "三国镇海";
            //var keyword = "haoyongma";
            int? type = null;
            List<string> tags = null;
            //var tags = new List<string>() { "34" , "35", "37" };
            bool? isGood = null;
            bool? isQA = null;

            Guid? creatorId = null;
            Guid? circleUserId = null;
            Guid? loginUserId = null;
            Guid? circleId = Guid.Parse("06d0b0a7-ff01-4aad-b36b-8fb58c0f6a8e");
            DateTime? startTime = null;
            DateTime? endTime = null;

            // var _all = esClient.SearchTopic(null, null, null, null, null, null, null, null, null, 1, 100);
            var data = SearchTopic(out long total, keyword, circleId, circleUserId, loginUserId, creatorId, type, isGood, isQA, tags, startTime, endTime, 1, 10);

            var resultTags = SearchTopicTags(keyword, circleId, circleUserId, loginUserId, creatorId, type, isGood, isQA, tags, startTime, endTime);

            var testTopic = new SearchTopic();
            testTopic.Tags = new List<SearchTopic.SearchTag>() {
                new SearchTopic.SearchTag()
            };

            DateTimeHelper.BaseToUtcTime(testTopic, true);
            foreach (var topic in data)
            {
                DateTimeHelper.BaseToUtcTime(topic);
            }
            var dd = 1;
        }


        public List<Func<QueryContainerDescriptor<SearchTopic>, QueryContainer>> GetSearchTopicQuery(string keyword, Guid? circleId, Guid? creator, int? type, bool? isGood, bool? isQA,
            List<string> tags, DateTime? startTime, DateTime? endTime)
        {
            var mustQuery = new List<Func<QueryContainerDescriptor<SearchTopic>, QueryContainer>>() {
                q => q.Term(tq => tq.Field(s => s.IsDeleted).Value(0)),
                q => q.Term(tq => tq.Field(s => s.Status).Value(0))
            };

            #region mustQuery

            if (circleId != null)
                mustQuery.Add(q => q.Term(tq => tq.Field(s => s.CircleId).Value(circleId.Value)));

            if (creator != null)
                mustQuery.Add(q => q.Term(tq => tq.Field(s => s.Creator).Value(creator.Value)));

            if (type != null)
                mustQuery.Add(q => q.Term(tq => tq.Field(s => s.Type).Value(type.Value)));

            if (true == isGood)
                mustQuery.Add(q => q.Term(tq => tq.Field(s => s.IsGood).Value(1)));

            if (true == isQA)
                mustQuery.Add(q => q.Term(tq => tq.Field(s => s.IsQA).Value(1)));

            //标签查询
            if (tags != null && tags.Count != 0)
                mustQuery.Add(q => q.Nested(nq =>
                        nq.Path(s => s.Tags)
                        .Query(qq =>
                            qq.Terms(s => s.Field("tags.id").Terms(tags))
                        )));

            //模糊查询
            if (!string.IsNullOrWhiteSpace(keyword))
            {
                var query = new MultiMatchQuery()
                {
                    Fields = Infer.Field<SearchTopic>(f => f.Content),
                    Query = keyword
                };

                //mustQuery.Add(q => query || q.Match(s => s.Field("tags").Query(keyword)));
                mustQuery.Add(q => query ||
                    q.Nested(nq =>
                        nq.Path(s => s.Tags)
                        .Query(qq =>
                            qq.Match(s => s.Field("tags.name").Query(keyword))
                        )));
            }

            //时间查询
            if (startTime != null)
            {
                var query = new DateRangeQuery()
                {
                    Field = Infer.Field<SearchTopic>(f => f.Time),
                    GreaterThan = startTime
                };
                mustQuery.Add(q => query);
            }
            if (endTime != null)
            {
                var query = new DateRangeQuery()
                {
                    Field = Infer.Field<SearchTopic>(f => f.Time),
                    LessThan = endTime
                };
                mustQuery.Add(q => query);
            }
            #endregion
            return mustQuery;
        }

        public Func<QueryContainerDescriptor<SearchTopic>, QueryContainer> GetSearchTopicFilter(Guid? circleUserId, Guid? loginUserId)
        {
            #region filter
            //未登录只能查公开的话题
            if (loginUserId == null || loginUserId == Guid.Empty)
            {
                return q => q.Bool(b => b.MustNot(qq => qq.Exists(tq => tq.Field(s => s.OpenUserId))));
            }
            //登录后, 可以查看公开的 和 自己的话题
            else if (loginUserId != circleUserId)
            {
                return q =>
                     q.Bool(b =>
                         b.MustNot(qq => qq.Exists(tq => tq.Field(s => s.OpenUserId)))
                         )
                     || q.Bool(b =>
                         b.Must(qq => qq.Term(qt => qt.Field(s => s.Creator).Value(loginUserId.Value)))
                         )
                     ;
            }
            //圈主可以查看自己圈子的所有的话题
            //circleId
            #endregion
            return null;
        }

        public List<SearchTopic> SearchTopic(out long total, string keyword, Guid? circleId, Guid? circleUserId, Guid? loginUserId, Guid? creator, int? type, bool? isGood, bool? isQA,
            List<string> tags, DateTime? startTime, DateTime? endTime, int pageNo = 1, int pageSize = 10)
        {
            int from = (pageNo - 1) * pageSize;
            int size = pageSize;
            var search = new SearchDescriptor<SearchTopic>()
                .Index(_index.SearchIndex)
                //加精的, 根据加精时间逆序
                .Sort(sd => isGood == true ? sd.Descending(SortSpecialField.Score).Descending(f => f.GoodTime) : sd.Descending(SortSpecialField.Score).Descending(f => f.LastReplyTime))
                .From(from)
                .Size(size)
                .Highlight(h => h.Fields(fs => fs.Field(f => f.Content))) //和查询一致,  content 或者 content.pinyin
                ;

            var filter = GetSearchTopicFilter(circleUserId, loginUserId);
            var mustQuery = GetSearchTopicQuery(keyword, circleId, creator, type, isGood, isQA, tags, startTime, endTime);


            if (mustQuery.Count != 0 && filter != null)
                search.Query(q => q.Bool(b => b.Must(mustQuery)) && q.Bool(b => b.Must(filter)));
            else if (mustQuery.Count != 0)
                search.Query(q => q.Bool(b => b.Must(mustQuery)));
            else if (filter != null)
                search.Query(q => q.Bool(b => b.Must(filter)));


            var result = _client.GetClient().Search<SearchTopic>(search);


            total = result.Total;
            var data = result.Hits.Select(s => s.Source).ToList();
            var highs = result.Hits.Select(s => s.Highlight).ToList();

            HighlightHelper.SetHighlights(ref data, highs, "Content");

            return data;
        }

        public List<int> SearchTopicTags(string keyword, Guid? circleId, Guid? circleUserId, Guid? loginUserId, Guid? creator, int? type, bool? isGood, bool? isQA,
            List<string> tags, DateTime? startTime, DateTime? endTime)
        {
            var search = new SearchDescriptor<SearchTopic>()
                .Index(_index.SearchIndex)
                .From(0)
                .Size(0)
                .Aggregations(aggs => aggs.Nested("aggs_tags", naggs => naggs.Path(topic => topic.Tags).Aggregations(
                    caggs => caggs.Terms("aggs_tags_id", s => s.Field("tags.id").Size(100))
                  )))
                ;

            var filter = GetSearchTopicFilter(circleUserId, loginUserId);
            var mustQuery = GetSearchTopicQuery(keyword, circleId, creator, type, isGood, isQA, tags: null, startTime, endTime);


            if (mustQuery.Count != 0 && filter != null)
                search.Query(q => q.Bool(b => b.Must(mustQuery)) && q.Bool(b => b.Must(filter)));
            else if (mustQuery.Count != 0)
                search.Query(q => q.Bool(b => b.Must(mustQuery)));
            else if (filter != null)
                search.Query(q => q.Bool(b => b.Must(filter)));


            var result = _client.GetClient().Search<SearchTopic>(search);

            List<int> aggTags = new List<int>();
            if (result.Aggregations.Count > 0)
            {
                var indexAggs = (SingleBucketAggregate)result.Aggregations["aggs_tags"];
                var tagAggs = (BucketAggregate)indexAggs["aggs_tags_id"];
                foreach (var item in tagAggs.Items)
                {
                    var bucket = (KeyedBucket<object>)item;
                    if (int.TryParse(bucket.Key + "", out int tagId))
                    {
                        aggTags.Add(tagId);
                    }
                }

            }
            return aggTags;
        }


        public CreateIndexResponse CreateIndex()
        {
            var resp = _client.GetClient().Indices.Delete(_index.Name);
            var createIndexResponse = _client.GetClient().Indices.Create(_index.Name, c => c
                .Aliases(ad => ad.Alias(_index.Alias))
                .Settings(s => s
                    .Setting("max_ngram_diff", 20)
                    .Setting("max_result_window", 500000)
                    .Similarity(si => si
                        .BM25("un_norms_similarity", bm25 => bm25.B(0))
                    )
                    .Analysis(a => a
                        .CharFilters(cf => cf
                            .UserDefined("tsconvert", new TSConvertCharFilter("t2s", "#"))
                         )
                        .TokenFilters(tf => tf
                            .UserDefined("pinyin_max_filter",
                                new PinyinTokenFilter
                                {
                                    KeepSeparateFirstLetter = false,
                                    KeepJoinedFullPinyin = true,
                                    KeepFullPinyin = true,
                                    KeepOriginal = true,
                                    LimitFirstLetterLength = 16,
                                    Lowercase = true,
                                    RemoveDuplicatedTerm = true
                                })
                            .UserDefined("pinyin_filter",
                                new PinyinTokenFilter
                                {
                                    KeepFirstLetter = true,
                                    KeepSeparateFirstLetter = false,
                                    KeepJoinedFullPinyin = true,
                                    KeepFullPinyin = false,
                                    KeepOriginal = true,
                                    LimitFirstLetterLength = 16,
                                    Lowercase = true,
                                    RemoveDuplicatedTerm = true
                                })
                            .UserDefined("pinyin_search_filter",
                                new PinyinTokenFilter
                                {
                                    KeepFirstLetter = false,
                                    KeepSeparateFirstLetter = false,
                                    KeepJoinedFullPinyin = true,
                                    KeepFullPinyin = false,
                                    KeepOriginal = true,
                                    LimitFirstLetterLength = 16,
                                    Lowercase = true,
                                    RemoveDuplicatedTerm = true
                                })
                        )
                        .Analyzers(aa => aa
                            .Custom("ik_analyzer", ca => ca
                                 .CharFilters("tsconvert")
                                 .Tokenizer("ik_max_word")
                                 .Filters("lowercase")
                             )
                            .Custom("ik_search_analyzer", ca => ca
                                 .CharFilters("tsconvert")
                                 .Tokenizer("ik_smart")
                                 .Filters("lowercase")
                             )
                            .Custom("ik_pinyin_analyzer", ca => ca
                                 .CharFilters("tsconvert")
                                 .Tokenizer("ik_max_word")
                                 .Filters("pinyin_max_filter", "lowercase")
                             )
                            .Custom("ik_pinyin_search_analyzer", ca => ca
                                 .CharFilters("tsconvert")
                                 .Tokenizer("ik_smart")
                                 .Filters("pinyin_search_filter", "lowercase")
                             )
                        )
                ))
                .Map<SearchTopic>(mm => mm
               .Dynamic(false)
                   .Properties(p => p
                       .Keyword(t => t.Name(n => n.Id))
                       .Keyword(t => t.Name(n => n.CircleId))
                       .Keyword(t => t.Name(n => n.OpenUserId))
                       .Number(t => t.Name(n => n.Type))
                       .Number(t => t.Name(n => n.Status))
                       .Number(t => t.Name(n => n.IsQA))
                       .Number(t => t.Name(n => n.TopType))
                       .Date(t => t.Name(n => n.TopTime))
                       .Number(t => t.Name(n => n.IsGood))
                       .Date(t => t.Name(n => n.GoodTime))
                       .Number(t => t.Name(n => n.LikeCount))
                       .Number(t => t.Name(n => n.FollowCount))
                       .Number(t => t.Name(n => n.ReplyCount))
                       .Nested<SearchTopic.SearchTag>(np =>
                            np.Name(n => n.Tags)
                            .Properties(tp =>
                                tp.Number(t => t.Name(n => n.Id))
                               .Text(t => t
                                    .Name(n => n.Name)
                                    .Fields(ff =>
                                        ff.Text(fk => fk
                                             .Name("pinyin")
                                             .Analyzer("ik_pinyin_analyzer")
                                             .SearchAnalyzer("ik_pinyin_search_analyzer")
                           )))))
                       .Keyword(t => t.Name(n => n.Creator))
                       .Date(t => t.Name(n => n.CreateTime))
                       .Date(t => t.Name(n => n.LastReplyTime))
                       .Date(t => t.Name(n => n.LastEditTime))
                       .Date(t => t.Name(n => n.Time).Format(""))
                       .Number(t => t.Name(n => n.IsDeleted))
                       .Text(t => t
                           .Name(n => n.Content)
                           .Fields(f => f.Text(ft
                                 => ft.Name("pinyin")
                                 //设置keyword的相关性
                                 //.Similarity("un_norms_similarity")
                                 .Analyzer("ik_pinyin_analyzer")
                                 .SearchAnalyzer("ik_pinyin_search_analyzer")
                           ))
                       )
                   )
               )
            );
            return createIndexResponse;
        }
    }
}
